/**
 * @author Ahammer
 * @date   2010 05
 * @update 2010 09 erode of a constant image did not change the image any more
 *                 therefore, sometimes images did not converge to totally black
 *                 now it stops eroding if the image is constant
 * @update 2011 01 added 16 bit support  
 */

/*
 * This file is part of Iqm.
 * Copyright (c) 2010-2011 Helmut Ahammer
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package op;

import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.awt.image.renderable.ParameterBlock;
import java.util.Arrays;
import javax.media.jai.BorderExtender;
import javax.media.jai.DataBufferFloat;
import javax.media.jai.Histogram;
import javax.media.jai.JAI;
import javax.media.jai.KernelJAI;
import javax.media.jai.ParameterBlockJAI;
import javax.media.jai.PlanarImage;
import javax.media.jai.RasterFactory;
import javax.media.jai.RenderedOp;
import javax.media.jai.TiledImage;

import main.IqmTools;

/**
 * This is the main image processing class
 * There exist two approaches:
 * A user defined JAI operator is just called 
 * or:
 * The actual processing is implemented in this class 
 */
public class IqmDistMapOperator{ 


  public IqmDistMapOperator() {
	  //WARNING: Don't declare fields here
	  //Fields declared here aren't thread safe!   
  }
  
  /**
   * This method tests if image has a constant grey value
   * @param  PlanarImage pi 
   * @return isConstant boolean
   */
  private boolean isConstant(PlanarImage pi) {
	  double typeGreyMax = IqmTools.getImgTypeGreyMax(pi);
	  ParameterBlock pb = new ParameterBlock();  
	  pb.addSource(pi);  
	  pb.add(null); //ROI
	  pb.add(1); //Sampling
	  pb.add(1); 
	  //pb.add(new int[]{(int)typeGreyMax+1}); //Number of bins
	  pb.add(new int[]{(int)typeGreyMax, (int)typeGreyMax, (int)typeGreyMax}); //Number of bins
	  pb.add(new double[]{0d, 0d, 0d}); //Start
	  pb.add(new double[]{typeGreyMax, typeGreyMax, typeGreyMax}); //End
	  //pb.add(new double[]{typeGreyMax + 1}); //End
	  RenderedOp rendOp = JAI.create("histogram", pb);  
	  Histogram histo = (Histogram) rendOp.getProperty("histogram");

	  boolean isConstant = false;
	  for (int i = 0; i < typeGreyMax; i++){
		  //System.out.println("IqmDistMapOperator i: " +1);
		  int total = histo.getSubTotal(0, i, i);
		  //if (total != 0 )System.out.println("IqmDistMapOperator total: " + total);
		  if (total == pi.getWidth()* pi.getHeight()){ 
				  isConstant = true; //image with constant grey value found
				  break;
				  
		  }else{
				  isConstant = false;
		  }
	  }
	  return isConstant;
  }
  /**
   * 
   * @param ParametrBlockJAI pb
   */
  public Object run(ParameterBlockJAI pbJAI){
	  Object ob = null;
//    ob = JAI.create("IqmDistMap".toLowerCase(), pb, null);  
	  
	  PlanarImage pi    = (PlanarImage) pbJAI.getRenderedSource(0);
	  int kernelShape   = pbJAI.getIntParameter("KernelShape");
	  int kernelSize    = pbJAI.getIntParameter("KernelSize");
	  int resultOptions = pbJAI.getIntParameter("ResultOptions");
	   	  
	  String imgName = (String) pi.getProperty("image_name");
	  String fileName = (String) pi.getProperty("file_name");
	  int width = pi.getWidth();
	  int height = pi.getHeight();
	  
	  String type = IqmTools.getImgTyp(pi);
	  double typeGreyMax = IqmTools.getImgTypeGreyMax(pi);
	  
  	  KernelJAI kernelJAI = null;
	  int kernelWidth  = kernelSize;
	  int kernelHeight = kernelSize;
	  if (kernelShape == 0){
		  //kernelJAI = KernelFactory.createRectangle(kernelWidth, kernelHeight);
		  int size = kernelSize*kernelSize;
		  float[] kernel = new float[size];
		  Arrays.fill(kernel, 0.0f);  //Damit Hintergrund nicht auf 1 gesetzt wird
		  kernelJAI = new KernelJAI(kernelSize, kernelSize, kernel); 
	  }
	  if (kernelShape == 1){
		 //kernel = KernelFactory.createCircle((kernelSize-1)/2); //Size = radius*2 +1
		 int size = kernelSize*kernelSize;
		 float[] kernel = new float[size];
		 //Arrays.fill(kernel, 1.0f);
		 for (int i = 0; i < kernelSize; i++) {
				  for ( int j = 0; j < kernelSize; j++) {
					  if ((i == (kernelSize/2)) && (j == (kernelSize/2))) {
						  kernel[i*kernelSize + j] = 0.0f;
					  }else{
						  kernel[i*kernelSize + j] = 0.0f;					  
					  }
				  }
		  }
	      kernelJAI = new KernelJAI(kernelSize, kernelSize, kernel); 
	  }
	  
	  //Optional print kernel
	  //System.out.println(KernelUtil.kernelToString(kernelJAI, true));
	  
	  ParameterBlock pb = new ParameterBlock();
	  RenderingHints rh = new RenderingHints(JAI.KEY_BORDER_EXTENDER, BorderExtender.createInstance(BorderExtender.BORDER_COPY));

	  // create empty image
	  //PlanarImage resultImage = ConstantDescriptor.create((float) width, (float) height, new Byte[]{0x0}, null);
  
	  SampleModel sm = RasterFactory.createBandedSampleModel(DataBuffer.TYPE_FLOAT, width, height, 1);  //1 banded sample model
      float[] floatArr = new float[width*height];
	  DataBufferFloat db = new DataBufferFloat(floatArr, width*height);    
	  WritableRaster wrOut = RasterFactory.createWritableRaster(sm, db, new Point(0,0));
	  // create tiled image
	  ColorModel cm = PlanarImage.createColorModel(sm); //compatible color model
	  TiledImage tiOut = new TiledImage(0, 0, width, height, 0, 0, sm, cm);
	  tiOut.setData(wrOut);  
	  PlanarImage piOut = tiOut.createSnapshot();
	  
	  ParameterBlock pbErode = null;
	  //pbErode.add(kernelJAI);
	  ParameterBlock pbAdd   = null;
	  
	  while (!isConstant(pi)) {
      //for (int i = 1; i <= 200; i++ ) { 
    	  pbErode = new ParameterBlock();
    	  pbErode.add(kernelJAI);
		  pbErode.setSource(pi, 0);
		  pi = JAI.create( "erode", pbErode, rh); 
		  //erode of a constant image doesn't change image
		  //therefore, the loop leads sometimes to an image with a constant grey value > zero 
		  //these images will never be totally black!
		  
		  pbAdd = new ParameterBlock();
		  pbAdd.setSource(pi,    0);
		  pbAdd.setSource(piOut, 1);  
		  piOut = JAI.create( "Add", pbAdd, null);
	  }	  
	  ob = piOut;
	  
	  if (resultOptions == 0){ //clamp to byte
	      pb = new ParameterBlock();
		  pb.addSource(ob);
		  if (type == "RGB")    pb.add(DataBuffer.TYPE_BYTE);
		  if (type == "8 bit")  pb.add(DataBuffer.TYPE_BYTE);
		  if (type == "16 bit") pb.add(DataBuffer.TYPE_USHORT);
		  ob = JAI.create("format", pb);  	  
	  }
	  if (resultOptions == 1){ //normalize to type
	        pb = new ParameterBlock();
			pb.addSource(ob);
			RenderedOp extrema = JAI.create("extrema", pb);
		    double[] minVec = (double[])extrema.getProperty("minimum");
		    double[] maxVec = (double[])extrema.getProperty("maximum");
		    double min = minVec[0];
		    double max = maxVec[0];
		
		    pb = new ParameterBlock();
			pb.addSource(ob);
		    pb.add(new double[] {(typeGreyMax/(max-min))}); //Rescale 
		    pb.add(new double[] {((typeGreyMax*min)/(min-max))}); //offset
			ob = JAI.create("rescale", pb); 
		 
		    pb = new ParameterBlock();
			pb.addSource(ob);
			if (type == "RGB")    pb.add(DataBuffer.TYPE_BYTE);
			if (type == "8 bit")  pb.add(DataBuffer.TYPE_BYTE);
			if (type == "16 bit") pb.add(DataBuffer.TYPE_USHORT);
			ob = JAI.create("format", pb);  	 
	  }

	  if (resultOptions == 2){ //Actual
		  //does nothing
	  }
	  
	  ((PlanarImage)ob).setProperty("image_name", imgName);	 
	  ((PlanarImage)ob).setProperty("file_name", fileName);	
	  return ob;
  }
}
